########################################################################
# filepicker.py - GUI code used only when wickddup is run in "file
#	picker" mode.  The main dialog has file upload info input fields,
#	the slate info input fields, and a file list.  The user can add
#	files to that list to set up the upload process.
#
# Created 2006.07.15 by Warren Young
#
# Copyright (c) 2006 by Pixel Corps.  This program may be used under
# the terms of the X11 license, a copy of which should have accompanied
# this program, in the LICENSE file.
########################################################################

import fncheck, gui, iofield, localparms, mainframe, prefs, slate, util

import wx

import glob, os, posixpath, sys


#### class FilePickerWindow ############################################

class FilePickerWindow(mainframe.MainFrame):
	""" 
	Class defining the file picker / slate info gathering window, shown
	before the upload status window when wickddup is run with the -g flag.
	""" 

	fncheckBaseID = 1000

	def __init__(self, uploader):
		"""
		Creates the file picker window.

		@type uploader: SFTPUploader
		@param uploader: set of uploading parameters as gathered by our
		caller. This object will expand on these based on user inputs in
		the file picker window, and pass them on to the SFTP component
		of the program.
		"""
	
		# Call parent class to handle basics common to all wickddup
		# top-level windows.
		mainframe.MainFrame.__init__(self, uploader)

		# Init misc instance variables
		self.lastWhyDisable = None

		# Load user preferences
		self.prefs = prefs.Preferences()

		# Set up input field state variables that let us know when we
		# have enough info that we can enable the Upload button.
		self.fileCount = 0

		# Set ourselves as the event handler for input field changes
		iofield.IOField.eventHandler = self

		# Create panel to hold everything
		self.panel = iofield.IOField.parent = wx.Panel(self)
		
		# Figure out default upload directory values, depending on
		# whether it appears to be parameterized.
		if self.prefs.remoteDirectory:
			remoteDirValue = self.prefs.remoteDirectory
		else:
			remoteDirValue = localparms.parms['RemoteDirectoryDefault']
			self.prefs.remoteDirectory = remoteDirValue
		remoteDirVarValue = self.prefs.remoteDirectoryVariable
		if '%s' in remoteDirValue and not remoteDirVarValue:
			remoteDirVarValue = self.prefs.userName or \
					os.environ['USER'] or os.environ['USERNAME'] or ''
			self.prefs.remoteDirectoryVariable = remoteDirVarValue

		# Define upload parameter edit control set
		self.uploadBoxSizer = wx.StaticBoxSizer(wx.StaticBox(self.panel,
				wx.ID_ANY, 'SFTP Server Info'), wx.VERTICAL)
		self.remoteDirVarLabel = localparms.parms['RemoteDirectoryVariable']
		self.uploadFields = iofield.FieldSet(
				iofield.InputField('User name', 10, 'e.g. wyoung',
						handler = self.OnChangeUserName,
						value = self.prefs.userName),
				iofield.InputField('Host name', 25,
						handler = self.OnChangeHostName,
						value = self.prefs.hostName),
				iofield.InputField('Remote directory', 25,
						handler = self.OnChangeRemoteDir,
						value = remoteDirValue),
				iofield.InputField(self.remoteDirVarLabel, 25, 
						handler = self.OnChangeRemoteDirVariable,
						value = remoteDirVarValue),
			)
		self.CreateFieldSet(self.uploadFields, self.uploadBoxSizer)

		# Build box holding branding image (falls back to a text
		# representation).  We do this out of order because we need the
		# previous sizer to be filled out so we can decide how big to
		# make the bitmap.
		self.brandImageSizer = util.GetBanner(self.panel,
				self.uploadBoxSizer)

		# Define slate parameter edit control set
		self.slateBoxSizer = wx.StaticBoxSizer(wx.StaticBox(self.panel,
				wx.ID_ANY, 'Slate Info'), wx.VERTICAL)
		self.slateFields = iofield.FieldSet(
				iofield.InputField('Project name', 25,
						handler = self.OnChangeProjectName,
						value = self.prefs.projectName),
				iofield.InputField('Scene name', 25,
						handler = self.OnChangeSceneName,
						value = self.prefs.sceneName),
				iofield.InputField('Element name', 25,
						handler = self.OnChangeElementName,
						value = self.prefs.elementName),
				iofield.InputField('Artisan name', 25,
						handler = self.OnChangeArtisanName,
						value = self.prefs.artisanName),
				iofield.InputField('Notes', width = 25, height = 2),
			)
		self.CreateFieldSet(self.slateFields, self.slateBoxSizer)

		# Define file name checking edit control set
		self.fnCheckBoxSizer = wx.StaticBoxSizer(wx.StaticBox(
				self.panel, wx.ID_ANY, 'File Name Checking'), wx.VERTICAL)
		self.fnCheckFields = iofield.FieldSet()
		i = 0
		for rule, label, width in zip(
				localparms.parms.fileNameCheckLiteralRules,
				localparms.parms.fileNameCheckLabels,
				localparms.parms.fileNameCheckWidths):
			if rule != "no":
				self.fnCheckFields.Append(iofield.InputField(
						label, width, id = self.fncheckBaseID + i,
						value = self.prefs.fileNameCheckLiterals[i],
						handler = self.OnChangeFileNameCheckField))
				fncheck.literals[i] = self.prefs.fileNameCheckLiterals[i]
			i += 1
		if len(self.fnCheckFields) > 0:
			self.CreateFieldSet(self.fnCheckFields, self.fnCheckBoxSizer)

		# Define file picker list box
		self.fileListBoxSizer = wx.StaticBoxSizer(
				wx.StaticBox(self.panel, wx.ID_ANY, 'Files to Upload'),
				wx.VERTICAL)
		self.fileListBox = wx.ListCtrl(self.panel, wx.ID_ANY,
				size = (0, 50), style = wx.LC_LIST | wx.SIMPLE_BORDER)
		self.fileListBoxSizer.Add(self.fileListBox,
				flag = wx.ALL | wx.EXPAND, border = 0)

		# Load file picker's icons
		il = wx.ImageList(16, 16, True)
		for base in ('document', 'folder'):
			filename = util.FindAppResource('%s-icon.png' % base)
			if filename:
				il.Add(wx.Bitmap(filename, wx.BITMAP_TYPE_PNG))
		if il.GetImageCount() > 0:
			self.fileListBox.AssignImageList(il, wx.IMAGE_LIST_SMALL)
			print 'PICKER: Loaded %d file list images.' % \
					il.GetImageCount()
		else:
			print 'PICKER WARNING: No file list box images found!'

		# Define the dialog's action buttons
		self.addFileButton = wx.Button(self.panel, wx.ID_ANY, "Add Files")
		self.addFolderButton = wx.Button(self.panel, wx.ID_ANY, "Add Folder")
		self.uploadButton = wx.Button(self.panel, wx.ID_ANY, "Upload")
		self.uploadButton.Disable()
		self.actionButtonSizer = wx.GridSizer(cols = 3,
				hgap = 10, vgap = 3)
		self.actionButtonSizer.Add(self.addFileButton,
				flag = wx.ALIGN_LEFT | wx.ALIGN_CENTER_VERTICAL)
		self.actionButtonSizer.Add(self.addFolderButton,
				flag = wx.ALIGN_CENTER_HORIZONTAL | wx.ALIGN_CENTER_VERTICAL)
		self.actionButtonSizer.Add(self.uploadButton,
				flag = wx.ALIGN_RIGHT | wx.ALIGN_CENTER_VERTICAL)

		# Assemble all sub-sizers into a 'main' sizer
		self.mainSizer = wx.BoxSizer(wx.VERTICAL)
		self.mainSizer.Add(self.brandImageSizer,
				flag = wx.ALL | wx.EXPAND, border = 8)
		self.mainSizer.Add(self.uploadBoxSizer,
				flag = wx.ALL | wx.EXPAND, border = 8)
		self.mainSizer.Add(self.slateBoxSizer,
				flag = wx.ALL | wx.EXPAND, border = 8)
		if len(self.fnCheckFields) > 0:
			self.mainSizer.Add(self.fnCheckBoxSizer,
					flag = wx.ALL | wx.EXPAND, border = 8)
		self.mainSizer.Add(self.fileListBoxSizer,
				flag = wx.ALL | wx.EXPAND, border = 8)
		self.mainSizer.Add(self.actionButtonSizer,
				flag = wx.ALL | wx.EXPAND, border = 8)

		# Finalize size and position of frame window
		self.panel.SetSizer(self.mainSizer)
		self.mainSizer.Fit(self)
		self.mainSizer.SetSizeHints(self)
		self.Center()

		# Bind controls to events
		self.Bind(wx.EVT_BUTTON, self.OnAddFileButton, self.addFileButton)
		self.Bind(wx.EVT_BUTTON, self.OnAddFolderButton, self.addFolderButton)
		self.Bind(wx.EVT_BUTTON, self.OnUploadButton, self.uploadButton)


	def AddFile(self, filename):
		"""
		Add a file to the upload set. Updates both the UI and the
		internal file list.

		@type filename: string
		@param filename: path to file to add
		"""
	
		if not filename in self.uploader.localFiles:
			print "PICKER: Added file '%s'" % filename
			self.uploader.localFiles.append(filename)
			self.fileListBox.InsertStringItem(sys.maxint,
					os.path.basename(filename), 0)


	def AddFolder(self, pathname):
		"""
		Add a folder to the upload set. Updates both the UI and the
		internal file list. The UI shows just the folder name, but
		internally we store the names of all regular files within
		the given folder. (Not recursive.)

		@type pathname: string
		@param pathname: path to folder to add
		"""
	
		basefolder = os.path.basename(pathname)
		startcount = len(self.uploader.localFiles)
		for file in glob.glob("%s/*" % pathname):
			if os.path.isfile(file) and \
					not file in self.uploader.localFiles:
				print "PICKER: Added file '%s'" % file
				self.uploader.localFiles.append(
						"%s::%s" % (basefolder, file))

		if startcount < len(self.uploader.localFiles):
			self.fileListBox.InsertStringItem(sys.maxint, basefolder, 1)
			return True
		else:
			return False


	def CheckVerificationFieldsFilled(self):
		i = 0
		for rule in localparms.parms.fileNameCheckLiteralRules:
			if rule != "no" and fncheck.literals[i] == '':
				return False
			i += 1

		return True


	def CreateFieldSet(self, fields, boxSizer):
		"""
		Given a set of labelled input and/or output fields, add those
		fields to the given sizer.

		@type fields: iofield.FieldSet
		@param fields: the set of fields to add to the sizer

		@type boxSizer: wx.BoxSizer
		@param boxSizer: the sizer to add the fields to
		"""

		fieldSizer = wx.FlexGridSizer(cols = 2, hgap = 10, vgap = 5)
		fieldSizer.AddGrowableCol(1)

		for f in fields:
			fieldSizer.Add(f.label,
					flag = wx.ALIGN_RIGHT | wx.ALIGN_CENTER_VERTICAL)
			fieldSizer.Add(f.field,
					flag = wx.ALIGN_LEFT | wx.ALIGN_CENTER_VERTICAL)

		boxSizer.Add(fieldSizer, flag = wx.ALL, border = 10)


	def GetFieldValue(self, name):
		"""
		Get the value in an input field given the field's name. This is
		just a wrapper around iofield.GetValueByName, using both the
		upload field set and the slate field set.

		@type name: string
		@param name: a case-insensitive fragment of the desired field's
		label.
		"""
	
		for fset in (self.uploadFields, self.slateFields):
			val = fset.GetValueByName(name)
			if val != None:
				return val
		print 'PICKER: Failed to look up value for field "%s"' % name
		return None


	def OnAddFileButton(self, event):
		"""
		Event handler for the Add File button. Pops up a file chooser
		dialog and lets the user select a file to add to the upload
		list.

		@type event: wx.CommandEvent
		@param event: information about the clicked button
		"""
	
		picker = wx.FileDialog(self, "Choose a File to Add",
				os.getcwd(), style = wx.OPEN | wx.MULTIPLE)
		if picker.ShowModal() == wx.ID_OK:
			for f in picker.GetPaths():
				self.AddFile(f)
			self.fileCount = self.fileCount + 1
			self.UpdateUploadButtonState()
		picker.Destroy()


	def OnAddFolderButton(self, event):
		"""
		Event handler for the Add Folder button. Pops up a folder
		chooser dialog and lets the user select a folder whose contents
		they wish to add to the upload list.

		@type event: wx.CommandEvent
		@param event: information about the clicked button
		"""
	
		picker = wx.DirDialog(self, "Choose a Folder to Add",
				os.getcwd())
		if picker.ShowModal() == wx.ID_OK and \
				self.AddFolder(picker.GetPath()):
			self.fileCount = self.fileCount + 1
			self.UpdateUploadButtonState()
		picker.Destroy()


	def OnChangeFileNameCheckField(self, event):
		"""
		Called when one of the file name checking edit fields change.
		Just stores the new value internally.

		@type event: wx.KeyEvent
		@param event: information about the changed input field
		"""
	
		index = event.GetId() - self.fncheckBaseID
		fncheck.literals[index] = event.GetString()
		self.prefs.fileNameCheckLiterals[index] = event.GetString()
		self.UpdateUploadButtonState()


	def OnChangeArtisanName(self, event):
		"""
		Called when the artisan slate info field changes.  Saves the new
		value internally.

		@type event: wx.KeyEvent
		@param event: information about the changed input field
		"""
	
		self.prefs.artisanName = self.GetFieldValue('artisan')


	def OnChangeElementName(self, event):
		"""
		Called when the element slate info field changes.  Saves the new
		value internally.

		@type event: wx.KeyEvent
		@param event: information about the changed input field
		"""
	
		self.prefs.elementName = self.GetFieldValue('element')


	def OnChangeHostName(self, event):
		"""
		Called when the host name SFTP info field changes.  Saves the new
		value internally, and decides if the Upload button's state needs
		to change.

		@type event: wx.KeyEvent
		@param event: information about the changed input field
		"""
	
		self.prefs.hostName = self.GetFieldValue('host')
		self.UpdateUploadButtonState()


	def OnChangeProjectName(self, event):
		"""
		Called when the project slate info field changes.  Saves the new
		value internally.

		@type event: wx.KeyEvent
		@param event: information about the changed input field
		"""
	
		self.prefs.projectName = self.GetFieldValue('project')


	def OnChangeRemoteDir(self, event):
		"""
		Called when the remote directory SFTP info field changes.  Saves
		the new value internally, and decides if the Upload button state
		needs to change.

		@type event: wx.KeyEvent
		@param event: information about the changed input field
		"""
	
		self.prefs.remoteDirectory = self.GetFieldValue('directory')
		self.UpdateUploadButtonState()


	def OnChangeRemoteDirVariable(self, event):
		"""
		Called when the remote directory variable SFTP info field changes.
		Saves the new value internally, and decides if the Upload button 
		state needs to change.

		@type event: wx.KeyEvent
		@param event: information about the changed input field
		"""
	
		self.prefs.remoteDirectoryVariable = self.GetFieldValue(
				self.remoteDirVarLabel)
		self.UpdateUploadButtonState()


	def OnChangeSceneName(self, event):
		"""
		Called when the scene slate info field changes.  Saves the new
		value internally.

		@type event: wx.KeyEvent
		@param event: information about the changed input field
		"""
	
		self.prefs.sceneName = self.GetFieldValue('scene')


	def OnChangeUserName(self, event):
		"""
		Called when the user name SFTP info field changes.  Saves the
		new value internally, and decides if the Upload button state
		needs to change.

		@type event: wx.KeyEvent
		@param event: information about the changed input field
		"""
	
		self.prefs.userName = self.GetFieldValue('user')
		self.UpdateUploadButtonState()


	def OnUploadButton(self, event):
		"""
		Event handler for the Upload button. Takes the information
		entered into the form and, if it's acceptable, begins the actual
		upload process.

		@type event: wx.CommandEvent
		@param event: information about the clicked button
		"""
	
		# Save input values to uploader object
		self.uploader.hostName = self.prefs.hostName
		self.uploader.remoteDirRaw = self.prefs.remoteDirectory
		self.uploader.remoteDirVar = self.prefs.remoteDirectoryVariable
		self.uploader.userName = self.prefs.userName

		# Figure out remote paths
		self.uploader.BuildRemotePaths()

		# Build slate, if we have all the info
		project = self.GetFieldValue('project')
		scene = self.GetFieldValue('scene')
		element = self.GetFieldValue('element')
		artisan = self.GetFieldValue('artisan')
		notes = self.GetFieldValue('notes')
		if project and scene and element and artisan and notes:
			slatefile = slate.Generate(project, scene, element, artisan,
					notes, self.uploader)
			if slatefile:
				print 'PICKER: found slate values, wrote %s' % slatefile
				self.uploader.localFiles.append(slatefile)
				self.uploader.remoteFiles.append(
						posixpath.join(self.uploader.RemoteDir(),
						os.path.basename(slatefile)))

		# Save entered values to prefs file so they're restored on the
		# next run.
		self.prefs.Save()

		# Start upload process
		print 'PICKER: starting upload, %s' % self.uploader
		gui.ShowUploadStatusWindow(self.uploader)


	def UpdateUploadButtonState(self):
		"""
		Examines the state of various variables reflecting the states of
		input fields to decide if the Upload button should be enabled or
		not.  Basically, this checks to see whether we have enough
		information to attempt an SFTP upload sequence.
		"""
	
		whyDisable = None
		if self.fileCount <= 0:
			whyDisable = 'no files'
		elif not self.prefs.userName:
			whyDisable = 'no user name'
		elif not self.prefs.hostName:
			whyDisable = 'no host name'
		elif not self.prefs.remoteDirectory:
			whyDisable = 'no upload dir'
		elif not self.CheckVerificationFieldsFilled():
			whyDisable = 'fncheck fields not all filled'

		if whyDisable:
			if whyDisable != self.lastWhyDisable:
				print 'PICKER: upload not allowed yet,', whyDisable
				self.lastWhyDisable = whyDisable
			self.uploadButton.Disable()
		else:
			self.uploadButton.Enable()

